其他
消息队列利器—MQProxy架构设计
背景:原来部门内没有专门的消息队列服务团队,且不同的研发团队使用的语言不同,在使用消息队列服务时,还需要各个研发团队关注底层消息队列系统的选型、协议以及使用方式,经常由于稳定性和容错机制等问题,导致业务服务受到影响。基础服务中台MQ团队基于kafka官方的提供java客户端,自研了分布式消息中间件MQProxy。用户可以通过简易的SDK轻松地接入MQProxy进行生产和消费,使用丰富、快捷、可靠的消息服务。
01
设计目标
底层消息队列系统的选型; 底层消息队列系统的协议; 底层消息队列系统的使用方式; 底层消息队列系统的健康状况与容错机制;
03
已实现功能
对常见的开源消息队列系统选型 统一使用研发人员更熟悉的HTTP协议、TCP协议 提供简单的produce/consume/commit API 关注消息队列系统的健康状况,实现必要的容错与可靠性
延迟队列:用于发送延时类请求,如:订单支付和消息推送等场景; 死信队列:用于保存用户消费失败的消息,以备查询和再次消费; 消息审计与Trace:目前依赖于网关日志和服务打点进行消息查询,跟踪,后续会通过日志采集来完成全链路跟踪; 超时重投与主动重投:根据需求,将用户在规定时间内消费但是没有提交的消息进行重新投递,用户也可以通过操作界面对死信消息进行主动投递; 消息回调:将用户发送到kafka中的数据主动投递给用户。避免用户在无消息时因空轮询耗费资源; 无限消费者协同:区别阿里云的kafka,自建kafka提供了消费者组自动注册功能,用户可以通过此功能临时创建多消费者组来实现多节点的消息广播,无需提前创建消费者组,代理也会定时清理长期不使用的临时消费者组; 机房多活:用户可以通过消费者代理同时消费两个机房同一个topic中的数据; 多语言SDK:提供php,golang,java的SDK,其他语言会在后续的研发中陆续推出。
03
MQProxy逻辑架构
生产者代理和消费者代理通过http协议,经过nginx网关服务于用户 生产者代理借助“延时队列模块”来发送延时消息。 消费者代理借助“位移合并模块”来解决多节点位移提交等问题。 “备份kafka”用来在“业务kafka”发生故障时作为临时存储。 mapdb为本地数据库,作用是在生产者代理向kafka代理发送前临时存储消息,避免代理发生宕机时丢失消息。 mysql和clickhouse分别用来存储集群状态和死信消息。 为方便用户使用,代理提供了php,go等客户端,并使用普罗米修斯和grafana监控自身和业务的健康状态。
ConsumerProxy的逻辑架构图如下:
如何实现重复消费?
预拉取消息列表; 已拉取但未提交的消息列表,代理使用java的delayQueue来保存未提交消息。用户从预拉取消息列表中拉取消息后,消息并未真正删除,而是投递到java的delayQueue中进行倒计时,当消息在delayQueue中倒计时结束后,如果仍需重新消费,则将消息重新投递到预拉取的消息列表中,供用户重复消费。
如何保证高吞吐量?
如何消除mqproxy对用户的状态?
如何实现多线程消费?
如何管理用户无序的消息提交?
如何实现高效率的位移合并?
如果消费者代理对提交的位移进行同步合并,则需要通过锁来保证提交的串行,这大大的降低了代理的吞吐量,也背离了代理多线程消费的初衷。 如果消费者代理对提交的位移进行异步合并,虽然提升了吞吐量,但当代理发生宕机或重启时,会丢失掉部分的提交数据,造成重复消费。
公共存储介质如何选择?
位移合并流程如下
代理预先在kafka中创建一些常用的固定时延级别的topic,如1s,5s,10s,1m,5m,10m,30m… 用户只能发送这些时延种类的消息。 当生产者代理消费到这些消息时,如:5s延时级别,则会将消息投递到delay-5的topic中。 mqproxy将从delay-5中消费到的消息拉取到内存中,在java的delayQueue中进行倒计时。(delayQueue是一种可以按照过期剩余时间排列的优先级队列) 当消息在java在的delayQueue中过期,mqproxy会将这些消息投递给真实的topic,并且向固定时延的topic-5提交位移。 因为在同一个的topic中,所有消息的延时级别是一样的,到期时间会自然而然的在每个parition中按照offset从小到大顺序排列,所以mqproxy向topic-5提交位移也是有序的。 因为提交位移的有序性,所以当mqproxy发生宕机后,不会发生丢消息的现象。
代理基于指定级别延时队列来实现任意级别延时队列; 代理预先在kafka中创建好一些topic,如:1s,2s,3s,…,9s,10s,20s,30s,…,90s,…,100s,200s,300s,…,900s,1000s,2000s,3000s,…,9000s,…,10000s,20000s,…,90000s。 当有消息投递过来后,代理会计算消息的延时级别,把他投递到一个指定级别的topic中。 如果在这个级别的消息过期,代理再将它投递到下一个级别的topic中。
04
未来规划:
mqproxy各个集群实例分布不均匀:目前我们一个mqproxy集群单独服务一个kafka集群,接入的十几个kafka集群中,有的集群消费实例很多(有100多个),而有的实例消费很少,(不到10个),这种情况导致消费实例很少的mqproxy集群资源浪费,但是消费实例很多的mqproxy集群资源紧张。 高级消费者:目前消费者代理采用高级消费者API进行消费,因为高级消费者在消费节点发生变化时,会产生分区再均衡,虽然我们前面一系列的操作对分区再均衡做了处理,但是当单个节点发生故障时,所有节点都会感知到,这降低了我们对系统整体稳定性的预期。 不支持顺序性和事务等高级功能:由于目前代理只能支持http协议传输,所以并不能支持消息的顺序性和事务等高级功能。 消费倾斜:当一个消费者代理多个分区,且QPS较高时,有几率会发生消费倾斜,虽然我们可以通过增加消费节点或者消费线程来解决这种情况,但是未能从根本消除隐患。
消费者资源池模式:我们不再为每个kafka的集群来单独搭建mqproxy,而是由一个大的mqproxy集群来服务所有kafka集群,即所有mqproxy的消费实例都放入这个mqproxy资源池进行管理,当有新的消费实例加入时,我们会从资源池中找到一个负载较低的资源进行资源分配,这样做的好处是,我们没有必要为一个负载较低但是很重要的业务单独搭建一套集群。 parition维度的管理:每个消费者实例管理粒度从对topic进行消费降级到对单个partition维度的消费,更细粒度的管理可以使系统更稳定,资源分配更加均匀,并且能从根本上解决消费倾斜带来的风险。 使用低级消费者API:高级消费者API因为支持了分区再均衡,所以当其中一个消费节点发生变更时,所有消费节点都有所感知,从而降低了系统的稳定性。低级消费者API配合分布式资源管理框架helix可以自主调控节点发生变更时的影响范围,从而降低了分区再均衡发生时的不确定性。 支持长连接:为了支持消息的顺序性和事务等高级功能,http连接显然不适合当下的所有场景,支持可靠的长连接服务是完成这些功能的先决条件。 多语言API支持:这是我们目前有所欠缺的,也欢迎有能力伙伴能够加入我们共同开发。
我知道你“在看”哟~